第十四章:綜合應用
1. 旅館清單 + 連動搜尋列實戰
這份檔案 (20260317-javascipt-ex02-hostlist.html) 是一個非常經典且完整的「前端資料處理與動態渲染」實戰範例。它完美結合了
Bootstrap 框架的排版、jQuery 的 AJAX 非同步請求,以及現代 JavaScript (ES6) 的進階陣列與字串處理技巧。
以下按照程式碼運作與建置的先後順序,為您拆解其中運用的核心語法與技巧:
步驟一:UI 架構建立與狀態預設 (HTML + Bootstrap)
在畫面呈現上,運用了 Bootstrap 的網格系統與表單元件來建構基礎骨架。
- 網格系統 (Grid System): 使用
row與col-md-6將兩個下拉選單(縣市、鄉鎮區)並排,下方使用col-md-10放置顯示結果的表格。 - 表單預設狀態控制: 在縣市與鄉鎮區的
<option>中加上selected disabled,製作出無法被當作有效值送出的「提示用選項」(例如:選擇縣市)。 - 關鍵防呆邏輯: 鄉鎮區的
<select>標籤預設加上了disabled屬性。在使用者尚未選擇「縣市」之前,強制鎖定「鄉鎮區」避免錯誤操作。
步驟二:非同步獲取外部資料 (AJAX GET)
網頁載入完成 ($(function() {...})) 後,程式立刻發起兩支獨立的 AJAX 請求來索取 JSON 資料,並儲存至全域變數中供後續使用。
$.ajax()請求: 設定type: "GET"分別讀取CityCountyData.json(縣市區域資料)與HotelList.json(旅館資料)。- 預先渲染第一層選單: 在成功取得
CityCountyData後,立刻使用forEach迴圈搭配 HTML 字串拼接,將「縣市」的選項填入第一個下拉選單 (#city) 中。
步驟三:資料清洗與正規化 (Data Normalization)
這是此範例中含金量最高的段落。從外部 API 抓回來的 HotelList 旅館資料可能存在各種瑕疵(例如欄位空缺、格式不一),程式透過自訂的
normalize(data) 函式進行了嚴格的資料清洗。
- 陣列映射
map(): 針對傳入的原始陣列,遍歷每一筆資料並回傳一個「重新格式化後」的全新陣列。 - 空值合併運算子
??(Nullish Coalescing): 例如(item.HotelName ?? "")。這用來攔截null或undefined的無效資料,如果遇到空值,就強制替換成空字串"",避免後續處理(如 trim)時程式崩潰。 - 字串去空白
.trim(): 將字串前後多餘的空白鍵消除,確保資料乾淨。 - 三元運算子
條件 ? 成立 : 不成立: 例如name === "" ? "未提供名稱" : name。進行最終防呆,如果清洗完發現字串是空的,就補上「未提供名稱」的預設文字,確保畫面上不會出現空白的破圖感。
步驟四:連動下拉式選單實作 (Event Listener & Filter)
透過監聽選單的變動,達成「選了 A 縣市,才出現 A 縣市的 B 鄉鎮」的連動效果。
- 變更事件監聽
change(): 分別監聽#city與#town的選取動作。 - 陣列過濾
filter():- 當選擇縣市時: 使用
.filter()從全台資料中精準篩選出該縣市的AreaList(鄉鎮區清單),接著使用.empty()清空舊的鄉鎮區選單,再用forEach填入新的鄉鎮區選項。 - 解除鎖定: 透過
$("#town").prop("disabled", false);將原本鎖定的第二層選單打開。 - 當選擇鄉鎮區時: 再次使用
.filter(),透過「縣市等於所選縣市 且 鄉鎮等於所選鄉鎮」的雙重條件 (item.City === selected_city && item.Town === selected_town),從清洗好的旅館總表中篩選出最終結果。
- 當選擇縣市時: 使用
步驟五:動態畫面渲染 (DOM Manipulation)
取得篩選後的最終旅館名單後,呼叫 renderTable(result) 將資料畫到網頁上。
- 重置容器
.empty(): 每次重新渲染前,必須先清空#hostlist內舊有的<tr>節點。 - 樣板字面值 (Template Literals): 使用反引號
`輕鬆處理多行 HTML 字串,並透過${item.Name}將 JavaScript 變數無縫嵌入 HTML 結構中。 - 插入節點
.append(): 將組合好的每一列 HTML 字串,逐一塞入表格的<tbody>中,完成最終的視覺呈現。
2. 進階實戰:多重條件篩選與分頁系統 (Pagination)
這份檔案 (20260317-javascipt-ex03-hostlist-filter.html)
是前一個連動選單範例的升級版。核心亮點在於加入了「關鍵字搜尋」、將表格改為「卡片佈局」,並且實作了純前端的「分頁功能」。
以下是建置這個進階系統的核心步驟與語法解析:
步驟一:進階資料清洗與鏈式寫法 (Chaining)
在原本的 normalize() 函式中,除了補齊預設文字,還新增了圖片的過濾與安全取值機制。
- 可選串連 (Optional Chaining
?.):const imgURL = (item.Images[0]?.URL ?? "").trim();。因為有些旅館完全沒有圖片,若直接寫item.Images[0].URL會造成程式報錯停止。加上?.後,若陣列為空,就會安全地回傳 undefined,再由??轉換為空字串。 - 方法串連 (Method Chaining): 在
data.map(...).filter(...)中,先用 map 重新格式化所有資料,緊接著直接呼叫 filter,將ImgURL為「未提供圖片」的劣質資料直接剔除,確保畫面上每一張卡片都有圖片。
步驟二:多重條件篩選中心 (Apply Filter)
這是一個非常經典的過濾器設計,將「縣市」、「鄉鎮」、「關鍵字」三個條件綜合判斷,決定該筆資料去留。
- 字串組合搜尋: 為了讓關鍵字能搜尋到所有細節,程式將名字、地址、電話、簡介全部拼接成一個大字串
text。 .includes(keyword): 檢查拼接好的大字串中,是否包含使用者輸入的關鍵字。- 布林值精簡判斷:
const matchCity = selected_city === "" || item.City === selected_city;。這句語法的精妙之處在於:如果使用者沒選縣市 (空字串),matchCity直接為 true (全部放行);如果有選,則必須精準吻合才為 true。最後三個條件使用&&(且) 必須同時成立。
步驟三:分頁邏輯實作 (Pagination Core)
純前端的分頁不依賴伺服器,而是利用陣列切割技術來達成。
- 全域變數控管: 宣告
currentPage = 1(當前頁碼) 與pageSize = 8(每頁 8 筆資料)。 - 計算總頁數
Math.ceil():Math.ceil(data.length / pageSize)。總筆數除以單頁筆數,並使用Math.ceil()無條件進位。例如 10 筆資料除以 8 等於 1.25,進位後就是 2 頁。 - 動態產生分頁列: 使用
for迴圈從 1 跑到總頁數,動態產生<li class="page-item">標籤。並利用三元運算子判斷目前是哪一頁,為其加上active類別產生高亮效果。
步驟四:陣列切割與畫面渲染 (Slice & Render)
決定好頁碼後,要把龐大的過濾資料「切」出目前該顯示的那一塊。
- 公式計算切割點:
startIndex = (currentPage - 1) * pageSize;endIndex = startIndex + pageSize;
- 陣列切割
.slice():const pageData = data.slice(startIndex, endIndex);。slice會回傳一個包含了被擷取元素的新陣列,不會修改到原始資料。我們只要把這個pageData丟進迴圈畫卡片即可。 - 事件委派監聽頁碼點擊: 因為分頁按鈕是動態生成的,必須使用
$(document).on("click", ".page-link", function(e){...})。 - 阻擋跳轉
e.preventDefault(): 點擊<a href="#">預設會讓網頁跳到最頂端,加上此語法可維持瀏覽體驗。
步驟五:卡片排版與字串截斷 (Card UI & Substring)
- CSS 背景圖技巧: 卡片的圖片不使用
<img>標籤,而是改用<div>並設定style="background-image: url(...)"。配合background-size: cover;,無論圖片原始比例為何,都能完美填滿 200px 高度的區塊不變形。 - 字串截斷
substring():item.HostWords.substring(0, 20)。旅館簡介可能長達幾百字,這會撐爆卡片。使用substring(0, 20)強制只擷取前 20 個字元,保持版面整齊劃一。
3. SPA-Single Page Application
這份 Single Page Application (SPA) 專案實戰範例整合了前端三大核心與第三方套件。以下為您將建置步驟與核心邏輯(包含註冊登入模組及 AJAX 深度解析)合併整理成的完整實作流程:
步驟一:基礎環境與外部資源掛載 (Environment Setup)
在網頁的 <head> 與 <body> 底部,引入多項外部資源來建構網頁基礎:
- Bootstrap 5 (bootstrap.min.css, bootstrap.bundle.min.js):作為全站的 RWD 排版與 UI 元件基礎。
- FontAwesome (all.min.css):提供如臉書、LINE、打勾圖示等向量 Icon 資源。
- Animate.css & WOW.js:兩者搭配使用,負責處理網頁往下滾動時的「進場動畫」。
- CounterUp2 (index.js):用於實作當畫面滾動到特定區塊時,數字會自動往上跳動的視覺特效。
- jQuery 4.0.0:用於簡化 DOM 的操作語法與執行 AJAX 非同步請求。
步驟二:自訂全域樣式與視覺輔助 (Custom CSS)
在 HTML 開發前,先在 <style> 中定義高頻率使用的共用樣式:
- 背景圖萬用類別 (.bg-cover):利用
background-position: center center;與background-size: cover;確保圖片完美填滿容器且不變形。 - 偽元素遮罩 (::before):在幻燈片 (
.carousel-item) 上疊加一層linear-gradient的漸層半透明黑色遮罩,確保上方的白色文字無論背景圖多亮都能清晰可見。 - 過場動畫 (transition):在團隊大頭照 (
.s08-userimg) 加上transition,配合:hover達成滑鼠移入時平滑放大並變為圓角的流暢互動感。
步驟三:網頁區塊切版與元件套用 (UI Construction)
運用 Bootstrap 網格與元件,依序建構各個網頁區塊:
- 自訂滿版幻燈片 (#s03):使用 Bootstrap Carousel 搭配自訂高度 (
85vh)。結合 WOW.js,在標題與按鈕上加上wow animate__bounceInLeft與animate__flash等類別,並利用data-wow-delay做出先後出現的層次感。 - 絕對定位圖文切割 (#s07, #s09):捨棄傳統的 6:6 網格切圖。在外層設定
position-relative,並將佔據 5 欄寬度的背景圖設定為position-absolute top-0 bottom-0,讓圖片不受容器限制,產生滿版且帶有俐落邊界的視覺效果。 - 語意化定義清單 (#s09):在收費方案介紹中,使用了 HTML 語意化標籤
<dl>(定義清單)、<dt>(定義項目)、<dd>(定義描述),提升網站的 SEO 友善度。 - 交錯時間軸 (#s05):利用 Bootstrap 的
flex-row-reverse類別,在 HTML 結構完全相同的情況下,讓偶數列的圖文自動左右對調。並利用 CSS 偽元素::before畫出絕對定位在正中央的實線與圓形節點。
這兩個模組獨立存在於 HTML 中 (
#registerModal 與 #loginModal),透過屬性綁定達成流暢切換:
- 觸發機制:導覽列右側按鈕透過
data-bs-toggle="modal"與data-bs-target直接綁定了對應的 Modal,無需撰寫額外的 JS。 - 二欄式排版:註冊視窗為了同時呈現「服務條款」與「註冊表單」,使用了
modal-xl(超大視窗)。內部modal-body透過row與col-6將畫面完美切割為左右兩半。 - 無縫視窗切換邏輯:在視窗底部 (如「已有帳號!」連結) 加入了
<a href="#" data-bs-toggle="modal" data-bs-target="#loginModal">。當使用者點擊時,Bootstrap 會自動關閉當前視窗並無縫銜接開啟新視窗。 - 視覺引導:註冊視窗標題使用
text-bg-warning(警告黃),登入視窗使用text-bg-success(成功綠),幫助使用者快速區分目前的作業狀態。
步驟四:動態特效與滾動監聽 (Scroll Animations)
透過 JavaScript 賦予網頁生命力與互動感:
- WOW.js 初始化:執行
new WOW().init();啟動滾動偵測,讓帶有wow類別的元素在進入螢幕視窗時才開始播放進場動畫。 - 交集觀察者 (IntersectionObserver API):這是一項進階的瀏覽器 API,用來監視特定元素 (如
.counter01)。當entry.isIntersecting為true(進入可視範圍) 時,才觸發counterUp函式,讓數字從 0 往上跳動,確保使用者絕對能看到完整的動畫過程。
步驟五:AJAX 非同步資料串接 (Data Fetching & Rendering)
團隊主持人介紹區塊 (#s08) 並非寫死,而是動態向第三方 API 索取資料的標準實作流程:
- 1. 發送請求與參數設定:在
$(function () { ... });(網頁 DOM 載入完成) 後立刻執行。使用$.ajax向https://randomuser.me/api/?results=12請求 12 筆資料,並將dataType設為'json',讓 jQuery 自動將回傳文字解析為 JS 物件。 - 2. 容器初始化與遍歷:成功回傳資料觸發
success後,先執行$("#s08-userlist").empty();清空 HTML 中的佔位內容,避免資料重複疊加。接著使用data.results.forEach(...)迴圈針對 12 筆資料逐一執行渲染邏輯。 - 3. 樣板字面值渲染:在迴圈內部,使用 ES6 反引號 (
`) 建立 HTML 結構。透過${item.picture.large}將高解析度大頭照動態注入<img>的src屬性;並透過${item.name.title} ${item.name.first}將稱謂與名字結合生成標題。 - 4. DOM 插入:最後利用
.append(strHTML),將動態組合好的 HTML 卡片標籤依序塞回#s08-userlist容器中,完成最終的畫面呈現。
4. OSM 開放街圖與叢集標記實戰 (Leaflet + MarkerCluster)
本實戰展示如何使用開源的 OpenStreetMap (OSM) 搭配 Leaflet.js 繪製互動式地圖,並利用 MarkerCluster 解決大量圖標重疊問題。
步驟一:環境建置與自訂叢集樣式 (Environment & CSS)
載入 Leaflet 與 MarkerCluster 的 CSS/JS 檔案,並為地圖準備一個具有明確高度的容器。
- 全螢幕容器: 使用 Bootstrap 的
vh-100類別,讓<div id="map">填滿螢幕。 - 掛載資源: 確實引入 Leaflet 核心檔案與
MarkerCluster.css。 - 自訂叢集顏色: 透過覆寫
.marker-cluster-small等類別的背景色,將預設配色改為更符合專案風格的視覺色彩。
步驟二:初始化 Leaflet 地圖與圖層 (Map Initialization)
網頁載入後,先設定中心點並準備好「叢集圖層」。
- 設定中心點與縮放: 使用
map = L.map('map').setView([緯度, 經度], 縮放層級)定位。 - 載入 OSM 圖磚: 使用
L.tileLayer載入 OpenStreetMap 的圖資圖片。 - 建立叢集群組: 宣告
new L.markerClusterGroup()作為未來標記的收納盒。
步驟三:使用 Axios 獲取資料 (Data Fetching)
捨棄 jQuery $.ajax,改用現代化的 Axios 套件來讀取包含旅館資訊的 JSON 資料。
axios.get('js/json/HotelList.json')
.then(function (response) {
// 成功取得資料後,將 response.data 丟入資料清洗函數
HotelList = normalize(response.data.Hotels);
// ...執行步驟五
})
.catch(function (error) {
console.log("讀取失敗:", error);
});
Axios 採用 Promise 架構,語法更簡潔且易於處理鏈式反應,且會自動將回傳的 JSON 字串解析為 JavaScript 物件(放在
response.data
中)。
步驟四:資料清洗與座標過濾 (Data Normalization)
這是地圖成功的關鍵。必須確保每一筆要渲染的資料都具有正確的「經緯度」,避免地圖報錯當機。
- 安全取值: 使用可選串連
?.抓取深層屬性(如圖片 URL)。 - 致命防呆 (Filter): 透過
.filter()剔除沒有Lat或Lng的無效旅館資料。
步驟五:產生標記並加入叢集 (Clustering & Rendering)
最後利用 forEach 遍歷清洗後的資料,將標記 (Marker) 畫在地圖上,並綁定氣泡視窗。
- 動態渲染: 將旅館名稱、地址、電話封裝入 Bootstrap
.card樣板。 - 綁定與收納 🎯 關鍵技巧:
不要直接
addTo(map),而是使用markers.addLayer(L.marker(lat_lng).bindPopup(strHTML))將標記收納進叢集群組。 - 視覺效果: 當資料點過度密集時,套件會自動將其融合成帶有數字的圓圈,縮放時再展開,提升質感與效能。
5. 視覺與開發好用資源匯整
在開發專案時,善用以下資源可大幅強化 UI/UX 體驗:
- WOW.js + Animate.css: 兩者結合後,能實作當網頁滾動到特定區塊時,才精準觸發進場動畫,增強互動層次感。
- Random User Generator: 免費 API,能快速產生包含頭像、姓名、信箱的隨機虛擬使用者資料,適合填補團隊介紹或留言區的排版。
- Font Awesome: 強大且成熟的免費 Icon 字體庫,提供豐富的功能圖示供開發者調用。